//
// files_UNIX.cpp
// libprotea
// Tools Library for the Protea Project / File management functionality, specific to UNIX
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../OS_defines.h"
#ifdef OS_UNIX

#include "../defines.h"
#include "files.h"
#include "../AppHandler/AppInterface.h"

int XCDeleteFile( const char* filename ) {
	return remove( filename );
};

int XCCopyFile( const char* source, const char* dest ) {
	return -1;	
};

int XCFileTransport::Send( const void* data, int size ) {
	INFUNC( XCFileTransport::Send, myapp );

	if (size == -1)
		size = strlen( (char*)data );

	int result = 0;

	if (myapp)
		myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, "\tSize to write: %d (cache left=%d)", size, FILE_BUFFER_SIZE - outused );

	if (size >= FILE_BUFFER_SIZE) {
		// Flush our current buffer, and write directly to file
		Flush();

		if (myapp)
			myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, "\tWrite direct: %d", size );

		char* pdata = (char*)data;
		ssize_t temp = 0;
		do {
			temp = write( file, pdata, size );
			result += temp;
			size -= temp;
			pdata += temp;
		} while (temp > 0 && size);

		if (temp <= 0)
			RAISEOSERROR();
	} else {
		// We are writing small data out


		// Are we small enough to fit into the buffer
		if (outused + size <= FILE_BUFFER_SIZE) {
			if (myapp)
				myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, "\tWrite cache: %d", size );
			memcpy( outbuffer + outused, data, size );
			outstart += size;
			outused += size;
			result = size;

//			DumpMemory( outbuffer, outused );
		} else {
			int temp = FILE_BUFFER_SIZE - outused;

			// Fill the buffer and flush it
			if (myapp)
				myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, "\t1) Write cache: %d (to file)", temp );
			memcpy( outbuffer + outstart, data, temp );
			outused += temp;
			outstart += temp;
			Flush();

			// Move the rest into the buffer
			size -= temp;
			if (myapp)
				myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, "\t2) Write cache: %d (to buffer)", size );
			memcpy( outbuffer, (char*)data + temp, size );

			outused = size; 
			outstart = size;
//			DumpMemory( outbuffer, outused );
		};
	};

	OUTFUNCRET( result );
};

int XCFileTransport::Recv( void* data, int size ) {
	INFUNC( XCFileTransport::Recv, myapp );

	int result = 0;

	int needsize = size;
	char* dataptr = (char*)data;

	if (myapp)
		myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, " Size requested: %d (cache left = %d)", needsize, inleft );

	while (needsize) {
		// If we have stuff cached, use it
		if (inleft > 0) {
			long temp;
			if (needsize > inleft)
				temp = inleft;
			else
				temp = needsize;

			if (myapp)
				myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, "\tUsing cache: %d (%d left)", temp, inleft-temp );

//			DumpMemory( inbuffer + instart, temp );
			memcpy( dataptr, inbuffer + instart, temp );
			dataptr += temp;
			needsize -= temp;
			result += temp;

			inleft -= temp;
			instart += temp;
		};
		
		if (inleft == 0 && needsize) {
			// We don't have enough buffer left, so read another chunk

			// If the chunk we need larger than our buffer size, if so, read directly
			if (needsize > FILE_BUFFER_SIZE) {
				if (myapp)
					myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, "\tDirect read: %d", needsize );
				long temp = read( file, dataptr, needsize );
				if (temp <= 0)
					RAISEOSERROR();

				needsize -= temp;
				result += temp;
			} else {
				inleft = read( file, inbuffer, FILE_BUFFER_SIZE );
				if (inleft <= 0)
					RAISEOSERROR();

				// Dump the input buffer
//				DumpMemory( inbuffer, FILE_BUFFER_SIZE );

				instart = 0;
			};
		};
	};

	OUTFUNCRET( result );
};

int XCFileTransport::Close() {
	if (file) {
		Flush();

		close( file );
		file = (PORT_HANDLE)0;
	};

	if (inbuffer) {
		delete [] inbuffer;
		inbuffer = NULL;
	};

	if (outbuffer) {
		delete [] outbuffer;
		outbuffer = NULL;
	};

	return 0;
};

void XCFileTransport::Open( const char* filename, eFileAccess readwrite, eFileMode mode ) {
	INFUNC( XCFileTransport::Open, myapp );

	if (myapp)
		myapp->ToolsTrace( TRC_APPINTERFACE, TLV_EVERYTHING, "Filename: %s", filename );

	file = open( filename, readwrite | mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
	if (file == -1)
		RAISEOSERROR_PARAM( filename );

	inbuffer = new char[ FILE_BUFFER_SIZE ];
	inleft = 0;
	instart = 0;

	outbuffer = new char[ FILE_BUFFER_SIZE ];
	outused = 0;
	outstart = 0;

	OUTFUNC();
};

bool XCFileTransport::Eof() {
	INFUNC( XCFileTransport::Eof, myapp );

	Flush();

	if (inleft)
		RETURNFUNC( false );

	OUTFUNCRET( GetPos() == GetSize() );
};

void XCFileTransport::Seek( long location, eFileSeekMode mode ) {
	INFUNC( XCFileTransport::Seek, myapp );

	inleft = 0;

	Flush();

	off_t result = lseek( file, location, mode );
	if (result == -1)
		RAISEOSERROR();
	
	OUTFUNC();
};

long XCFileTransport::GetPos() {
	Flush();

	return lseek( file, 0, SEEK_CUR );
};

long XCFileTransport::GetSize() {
	assert( file );

	long size;

	long current = GetPos();
	Seek( 0, seek_end );
	size = GetPos();
	Seek( current, seek_start );

	return size;
};

void XCFileTransport::Flush() {
	INFUNC( XCFileTransport::Flush, myapp );

	if (outused) {
//		DumpMemory( outbuffer, outused );

		char* data = outbuffer;
		int temp = 0;
		do {
			temp = write( file, data, outused );
			outused -= temp;
			data += temp;
		} while (temp > 0 && outused);

		if (temp == -1)
			RAISEOSERROR();

		outstart = 0;
	};

	OUTFUNC();
};

void XCFileTransport::Lock() {
	INFUNC( XCFileTransport::Lock, myapp );

	assert( file );

	errno = 0;
	struct flock lock;
	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_CUR;
	lock.l_start = (off_t)0;
	lock.l_len = (off_t)1;
	if (fcntl( file, F_SETLKW, &lock ) == -1)
		RAISEOSERROR();
	OUTFUNC();
};

void XCFileTransport::Unlock() {
	assert( file );

	errno = 0;
	struct flock lock;
	lock.l_type = F_UNLCK;
	lock.l_whence = SEEK_CUR;
	lock.l_start = (off_t)0;
	lock.l_len = (off_t)1;
	fcntl( file, F_SETLKW, &lock );
};

bool XCFileExists( const char* filename ) {
	int filedes = open( filename, O_RDONLY );
	if (filedes != -1)
		close( filedes );

	return (filedes != -1);
};

#endif // OS_UNIX
